// $Id: server.c 81 2011-04-24 07:37:10Z cedric.shih@gmail.com $
/*
 * Copyright (C) 2011 Cedric Shih <cedric.shih@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#include <nio.h>
#include <nio/buffer.h>
#include <nio/handler.h>

#include "log.h"
#include "request.h"
#include "response.h"

#include "codec/server.h"

struct niohttp_request_decoder *niohttp_request_decoder_new(void)
{
	struct niohttp_request_decoder *codec;

	niohttp_debug("constructing");

	if (!(codec = calloc(1, sizeof(*codec)))) {
		niohttp_error("failed to calloc");
		goto exception;
	}

	if (!(codec->decoder = niohttp_headers_decoder_new())) {
		niohttp_error("failed to create header decoder");
		goto exception;
	}

	return codec;

exception:
	niohttp_request_decoder_free(codec);
	return NULL;
}

void niohttp_request_decoder_free(void *obj)
{
	struct niohttp_request_decoder *codec = obj;

	niohttp_debug("destructing: %p", codec);

	if (!(codec)) {
		return;
	}

	if (codec->decoder) {
		niohttp_headers_decoder_free(codec->decoder);
	}

	free(codec);
}

int niohttp_request_decoder_setup(struct niohttp_request_decoder *decoder)
{
	int rc;

	niohttp_debug("setting up");

	if (!(decoder->request = niohttp_request_new())) {
		rc = NIOHTTP_ENOMEM;
		niohttp_error("failed to create request");
		goto finally;
	}

	decoder->state = NIOHTTP_REQ_LINE;
	decoder->mark = 0;
	decoder->offset1 = decoder->offset2 = -1;

	if ((rc = niohttp_headers_decoder_setup(decoder->decoder,
			decoder->request->header))) {
		niohttp_error("failed to setup header decoder");
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

void niohttp_request_decoder_teardown(struct niohttp_request_decoder *decoder)
{
	niohttp_debug("tearing down");

	niohttp_headers_decoder_teardown(decoder->decoder);

	if (decoder->request) {
		niohttp_request_free(decoder->request);
		decoder->request = NULL;
	}
}

int niohttp_request_decoder_parse(struct niohttp_request_decoder *decoder,
		struct niobuffer *buffer, struct niohttp_request **request)
{
	int rc;
	struct niohttp_headers *headers;

	while (decoder->state != NIOHTTP_REQ_CONTENT &&
			niobuffer_remaining(buffer) > 0) {
		switch (decoder->state) {
		case NIOHTTP_REQ_LINE:
			niohttp_debug("skipping parsed: %d", decoder->mark);

			if ((rc = niobuffer_skip(buffer, decoder->mark))) {
				niohttp_error("failed to skip buffer: %s",
						nio_strerror(rc));
				goto finally;
			}

			if ((rc = niohttp_request_decoder_parse_line(
					decoder, buffer))) {
				niohttp_error("failed to parse first line");
				goto finally;
			}

			niohttp_debug("marking parsed: %d", niobuffer_position(buffer));

			decoder->mark = niobuffer_position(buffer);
			break;
		case NIOHTTP_REQ_HEADERS:
			if ((rc = niohttp_headers_decoder_parse(
					decoder->decoder, buffer, &headers))) {
				niohttp_error("failed to parse headers: %s",
						niohttp_strerror(rc));
				goto finally;
			}

			if (headers) {
				decoder->state = NIOHTTP_REQ_CONTENT;
			}

			break;
		default:
			niohttp_error("unexpected state: %d", decoder->state);
			rc = -1;
			goto finally;
		}
	}

	if (decoder->state == NIOHTTP_REQ_CONTENT) {
		*request = decoder->request;
	} else {
		*request = NULL;
	}

	rc = 0;

finally:
	return rc;
}

int niohttp_request_decoder_parse_line(struct niohttp_request_decoder *decoder,
		struct niobuffer *buffer)
{
	int rc, b;
	char *data = (char *)niobuffer_array(buffer);
	size_t offset;

	niohttp_trace("parsing line");

	while (niobuffer_remaining(buffer) > 0) {
		if (decoder->offset1 == -1) {
			niohttp_trace("marking offset at: %d",
					niobuffer_position(buffer));
			decoder->offset1 = niobuffer_position(buffer);
		}

		if ((rc = niobuffer_get(buffer, &b))) {
			niohttp_error("failed to get buffer: %s",
					nio_strerror(rc));
			goto finally;
		}

		if ((b < ' ' || b > '~') && (b != '\r' && b != '\n')) {
			niohttp_error("invalid encoding");
			rc = -1;
			goto finally;
		}

		niohttp_trace("processing: %d", b);

		if (!decoder->request->method) {
			if (b == ' ') {
				offset = niobuffer_position(buffer) - 1;

				niohttp_trace("hit end of method: %d", offset);

				if ((rc = niohttp_request_nset_method(
						decoder->request,
						data + decoder->offset1,
						offset - decoder->offset1))) {
					niohttp_error("failed to set method: "
							"%s",
							niohttp_strerror(rc));
					goto finally;
				}

				decoder->offset1 = -1;
			}
		} else if (!decoder->request->uri) {
			if (b == ' ') {
				offset = niobuffer_position(buffer) - 1;

				niohttp_trace("hit end of uri: %d", offset);

				if ((rc = niohttp_request_nset_uri(
						decoder->request,
						data + decoder->offset1,
						offset - decoder->offset1))) {
					niohttp_error("failed to set uri: %s",
							niohttp_strerror(rc));
					goto finally;
				}

				decoder->offset1 = -1;
			}
		} else if (!decoder->request->protocol) {
			if (b == '/') {
				offset = niobuffer_position(buffer) - 1;

				niohttp_trace("hit end of protocol: %d",
						offset);

				if ((rc = niohttp_request_nset_protocol(
						decoder->request,
						data + decoder->offset1,
						offset - decoder->offset1))) {
					niohttp_error("failed to set protocol:"
							" %s",
							niohttp_strerror(rc));
					goto finally;
				}

				decoder->offset1 = -1;
			}
		} else if (!decoder->request->version.string) {
			if (b == '\r') {
				offset = niobuffer_position(buffer) - 1;

				niohttp_trace("hit end of version: %d", offset);

				if ((rc = niohttp_request_nset_version(
						decoder->request,
						data + decoder->offset1,
						offset - decoder->offset1))) {
					niohttp_error("failed to set version: "
							"%s",
							niohttp_strerror(rc));
					goto finally;
				}

				decoder->offset1 = -1;
			}
		} else {
			if (b != '\n') {
				niohttp_error("unexpected end char: %d", b);
				rc = -1;
				goto finally;
			}

			niohttp_trace("hit end of first line");

			decoder->offset1 = -1;
			decoder->state = NIOHTTP_REQ_HEADERS;
			break;
		}
		niohttp_trace("continuing");
	}

	rc = 0;

finally:
	return rc;
}

struct niohttp_response_encoder *niohttp_response_encoder_new(void)
{
	struct niohttp_response_encoder *codec;

	niohttp_debug("constructing");

	if (!(codec = calloc(1, sizeof(*codec)))) {
		niohttp_error("failed to calloc");
		goto exception;
	}

	return codec;

exception:
	niohttp_response_encoder_free(codec);
	return NULL;
}

void niohttp_response_encoder_free(void *obj)
{
	struct niohttp_response_encoder *codec = obj;

	niohttp_debug("destructing: %p", codec);

	if (!(codec)) {
		return;
	}

	free(codec);
}

inline int niohttp_response_encoder_setup(struct niohttp_response_encoder *encoder)
{
	return 0;
}

inline void niohttp_response_encoder_teardown(struct niohttp_response_encoder *encoder)
{

}

int niohttp_response_encoder_format(struct niohttp_response_encoder *encoder,
		struct niohttp_response *response, struct niobuffer *buffer)
{
	int rc;
	struct niohttp_headers_entry *entry;

	if (!response->protocol) {
		niohttp_error("missing protocol");
		rc = NIOHTTP_EINVAL;
		goto finally;
	}

	if (!response->version.string) {
		niohttp_error("missing version");
		rc = NIOHTTP_EINVAL;
		goto finally;
	}

	if (!response->reason) {
		niohttp_error("missing reason");
		rc = NIOHTTP_EINVAL;
		goto finally;
	}

	niohttp_debug("formatting response to buffer");

	niohttp_debug("putting first line");

	if ((rc = niobuffer_putf(buffer, "%s/%s %d %s\r\n",
			response->protocol, response->version.string,
			response->code, response->reason))) {
		niohttp_error("failed to put first line: %s", nio_strerror(rc));
		rc = NIOHTTP_EBUFFER;
		goto finally;
	}

	niohttp_debug("putting headers");

	RB_FOREACH(entry, niohttp_headers_tree, &response->header->tree) {
		if ((rc = niobuffer_putf(buffer, "%s: %s\r\n",
				entry->name, entry->value))) {
			niohttp_error("failed to put header: %s",
					nio_strerror(rc));
			rc = NIOHTTP_EBUFFER;
			goto finally;
		}
	}

	niohttp_debug("putting end of header");

	if ((rc = niobuffer_putf(buffer, "%s", "\r\n"))) {
		niohttp_error("failed to put end of header: %s",
				nio_strerror(rc));
		rc = NIOHTTP_EBUFFER;
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}
